/**
  ******************************************************************************
  * @author     Chris
  * @since      2024/6/25 13:24
  *
  * @file       pd_w25qxx.c
  * @brief      Xxx hardware driver.
  *
  * @note       This file contains the hardware driver for the Xxx.
  *
  * @warning    None.
  ******************************************************************************
  * Change Logs:
  *   Date          Author       Notes
  *   2024/6/25     Chris        the first version
  *
  ******************************************************************************
  */


#include "hd_w25qxx.h"

uint8_t W25QXX_FLASH_BUFFER[W25QXX_SECTOR_SIZE];


static void W25Qxx_init(W25Qxx *this) {
}

static void W25Qxx_readID(W25Qxx *this, uint8_t *MID, uint16_t *DID) {
    this->spi->start(this->spi, this->ssel);

    this->spi->swapByte(this->spi, W25QXX_JEDEC_ID);
    *MID = this->spi->swapByte(this->spi, W25QXX_DUMMY_BYTE);
    *DID = this->spi->swapByte(this->spi, W25QXX_DUMMY_BYTE);
    *DID <<= 8;
    *DID |= this->spi->swapByte(this->spi, W25QXX_DUMMY_BYTE);

    this->spi->stop(this->spi, this->ssel);
}

static void W25Qxx_writeEnable(W25Qxx *this) {
    this->spi->start(this->spi, this->ssel);

    this->spi->swapByte(this->spi, W25QXX_WRITE_ENABLE);

    this->spi->stop(this->spi, this->ssel);
}

static void W25Qxx_waitBusy(W25Qxx *this) {
    this->spi->start(this->spi, this->ssel);

    this->spi->swapByte(this->spi, W25QXX_READ_STATUS_REGISTER_1);

    uint32_t timeout = 100000;
    while (timeout--) {
        if ((this->spi->swapByte(this->spi, W25QXX_DUMMY_BYTE) & 0x01) != 0x01) {
            break;
        }
    }

    this->spi->stop(this->spi, this->ssel);
}

static void W25Qxx_read(W25Qxx *this, uint32_t address, uint8_t *dat, uint32_t count) {
    this->spi->start(this->spi, this->ssel);

    this->spi->swapByte(this->spi, W25QXX_READ_DATA);

    this->spi->swapByte(this->spi, address >> 16); // swap address[23:16]
    this->spi->swapByte(this->spi, address >> 8);  // swap address[15:8]
    this->spi->swapByte(this->spi, address);       // swap address[7:0]

    while (count--) {
        *dat++ = this->spi->swapByte(this->spi, W25QXX_DUMMY_BYTE);
    }

    this->spi->stop(this->spi, this->ssel);
}

static void W25Qxx_eraseSector(W25Qxx *this, uint32_t address) {
    W25Qxx_writeEnable(this);

    this->spi->start(this->spi, this->ssel);

    this->spi->swapByte(this->spi, W25QXX_SECTOR_ERASE_4KB);

    this->spi->swapByte(this->spi, address >> 16); // swap address[23:16]
    this->spi->swapByte(this->spi, address >> 8);  // swap address[15:8]
    this->spi->swapByte(this->spi, address);       // swap address[7:0]

    this->spi->stop(this->spi, this->ssel);

    W25Qxx_waitBusy(this);
}

static void W25Qxx_writePage(W25Qxx *this, uint32_t address, uint8_t *dat, uint16_t count) {
    W25Qxx_writeEnable(this);

    this->spi->start(this->spi, this->ssel);

    this->spi->swapByte(this->spi, W25QXX_PAGE_PROGRAM);

    this->spi->swapByte(this->spi, address >> 16); // swap address[23:16]
    this->spi->swapByte(this->spi, address >> 8);  // swap address[15:8]
    this->spi->swapByte(this->spi, address);       // swap address[7:0]

    while (count--) {
        this->spi->swapByte(this->spi, *dat++);
    }

    this->spi->stop(this->spi, this->ssel);

    W25Qxx_waitBusy(this);
}

static void W25Qxx_writeNoCheck(W25Qxx *this, uint32_t address, uint8_t *dat, uint16_t count) {
    uint16_t pageRemain = W25QXX_PAGE_SIZE - address % W25QXX_PAGE_SIZE;

    if (count <= pageRemain) pageRemain = count;

    while (1) {
        W25Qxx_writePage(this, address, dat, pageRemain);
        if (count == pageRemain) break;

        dat += pageRemain;
        address += pageRemain;

        count -= pageRemain;
        pageRemain = count > W25QXX_PAGE_SIZE ? W25QXX_PAGE_SIZE : count;
    }
}

static void W25Qxx_write(W25Qxx *this, uint32_t address, uint8_t *dat, uint16_t count) {
    uint16_t i;

    uint32_t sectorIndex = address / W25QXX_SECTOR_SIZE;       // sector index
    uint16_t sectorOffset = address % W25QXX_SECTOR_SIZE;      // offset of sector
    uint16_t sectorRemain = W25QXX_SECTOR_SIZE - sectorOffset; // remain + offset = W25QXX_SECTOR_SIZE

    if (count <= sectorRemain) sectorRemain = count;    // offset + count <= W25QXX_SECTOR_SIZE

    while (1) {
        // read all data of this sector
        this->read(this, sectorIndex * W25QXX_SECTOR_SIZE, W25QXX_FLASH_BUFFER, W25QXX_SECTOR_SIZE);

        // check the sector erase or not
        for (i = 0; i < sectorRemain; ++i) {
            if (W25QXX_FLASH_BUFFER[sectorOffset + i] != 0xFF) break;
        }

        if (i < sectorRemain) {
            // must erase, and then copy the data to buffer, finally, write that all
            this->eraseSector(this, sectorIndex * W25QXX_SECTOR_SIZE);
            for (i = 0; i < sectorRemain; ++i) W25QXX_FLASH_BUFFER[i + sectorOffset] = dat[i];
            W25Qxx_writeNoCheck(this, sectorIndex * W25QXX_SECTOR_SIZE, W25QXX_FLASH_BUFFER, W25QXX_SECTOR_SIZE);
        } else {
            // should erase noting, and write remaining data
            W25Qxx_writeNoCheck(this, address, dat, sectorRemain);
        }

        if (count == sectorRemain) break;   // write done, and exit

        sectorIndex++;
        sectorOffset = 0;
        dat += sectorRemain;
        address += sectorRemain;
        count -= sectorRemain;
        if (count > W25QXX_SECTOR_SIZE) sectorRemain = W25QXX_SECTOR_SIZE;
        else sectorRemain = count;
    }
}

static void W25Qxx_Storage_read(Storage *super, uint8_t *buffer, const uint32_t address, const uint32_t count) {
    W25Qxx *this = (W25Qxx *) super;
    this->read(this, address, buffer, count);
}

static W25Qxx *build(SPI *spi, GPIO *ssel) {
    W25Qxx *w25Qxx = malloc(sizeof(W25Qxx));

    Driver_add(w25Qxx, GEN_PERM);

    w25Qxx->spi         = spi;
    w25Qxx->ssel        = ssel;
    w25Qxx->init        = &W25Qxx_init;
    w25Qxx->getId       = &W25Qxx_readID;
    w25Qxx->read        = &W25Qxx_read;
    w25Qxx->eraseSector = &W25Qxx_eraseSector;
    w25Qxx->write       = &W25Qxx_write;

    Storage *storage = StorageBuilder.build();
    w25Qxx->storage      = *storage;
    w25Qxx->storage.read = &W25Qxx_Storage_read;

    return w25Qxx;
}

const struct W25QxxClass W25QxxBuilder = {.build = &build};


